package com.mathdroid.model.items;

import java.io.Serializable;
import java.util.LinkedList;

import com.mathdroid.model.EModelObserverEvent;
import com.mathdroid.model.Model;
import com.mathdroid.model.enums.EClass;
import com.mathdroid.model.enums.EItemSource;
import com.mathdroid.modules.ISpecProficiencies;

/**
 * This model contains the items for the current filter settings. Those items
 * are shown in the item browser panel.
 */
public final class ItemBrowserModel implements Serializable {

	/**
	 * Generated UID.
	 */
	private static final long serialVersionUID = 6397446342926929846L;

	/**
	 * The item factory can create new clean unfiltered item templates.
	 */
	private final transient ItemFactory f = new ItemFactory();
	
	/**
	 * FILTER: The type of the item.
	 */
	private String typeCondition = null;
	
	/**
	 * The regular expression used to filter items.
	 */
	private String regEx = "";
	
	/**
	 * Filter: The allowed sources.
	 */
	private final LinkedList<SourceCondition> sourceConditions = 
		new LinkedList<SourceCondition>();
	
	/**
	 * The items for the current filter settings. Those should only change on
	 * events, not "get"-calls.
	 */
	private ItemMap filteredItems = null;
	
	/**
	 * Create an item browser model.
	 * @param type Item type.
	 */
	public ItemBrowserModel(final String type) {
		this.typeCondition = type;
		this.initConditions();
	}
	
	/**
	 * Initial item browser models allow all sources.
	 */
	private void initConditions() {
		this.sourceConditions.clear();
		for (EItemSource source : EItemSource.getAllSources()) {
			this.sourceConditions.add(
				new SourceCondition(source.toString(), true, source.getIcon()));
		}
	}

	/**
	 * Get the items for the current filter settings.
	 * @return Filtered items.
	 */
	public ItemMap getFilteredItems() {
		if (this.filteredItems == null) {
			this.refresh();
		}
		return this.filteredItems;
	}
	
	/**
	 * Force a refresh. The items will be filtered and rated again.
	 */
	public void refresh() {
		this.filteredItems = new ItemMap();
		
		ItemMap items = this.f.get(this.typeCondition);
		
		if (items != null) {
			/*
			 * The following filter methods should be ordered in the following
			 * way: The first methods should remove the bigger selections of 
			 * items. This improves runtime.
			 */
			this.filteredItems = this.filterBySource(items);
			this.filteredItems = this.filterByProficiency(items);
			this.filteredItems = this.filterByClass(this.filteredItems);
			Model.getProfile().getClassModule().rateItems(
					this.filteredItems.values());
		}
	}

	/**
	 * Check if the current specialization has the proficiency for the items.
	 * Returns a new list without changing the original list.
	 * @param items The items to filter.
	 * @return Filtered items.
	 */
	private ItemMap filterByProficiency(final ItemMap items) {
		ItemMap filtered = new ItemMap();
		ISpecProficiencies p = Model.getESpec().getProficiencies();
		
		for (Item i : items.values()) {
			switch (i.getType()) {
			case ARMOR:
				if (p.hasArmorProficiency(i.getArmorProficiency())) {
					filtered.put(i.getName(), i);
				}
				break;
			case WEAPON:
				if (p.hasWeaponProficiency(i.getWeaponProficiency())) {
					filtered.put(i.getName(), i);
				}
				break;
			case MODULE:
				filtered.put(i.getName(), i);
				break;
			default:
				break;
			}
		}
		
		return filtered;
	}

	/**
	 * Filter the items by source. Returns a new list without changing the
	 * original list.
	 * @param items The items to filter.
	 * @return Filtered items.
	 */
	private ItemMap filterBySource(final ItemMap items) {
		ItemMap filtered = new ItemMap();
		
		for (Item item : items.values()) {
			for (EItemSource source : item.getSources()) {
				boolean valid = false;
				
				for (SourceCondition sc : this.sourceConditions) {
					if (sc.getSource().equals(source.toString())
							&& sc.isEnabled()) {
						valid = true;
					}
				}
				
				if (valid) {
					filtered.put(item.toString(), item);
				}
			}			
		}
		
		return filtered;
	}
	
	/**
	 * Filter the items by class. Returns a new list without changing the
	 * original list.
	 * @param items The items to filter.
	 * @return Filtered items.
	 */
	private ItemMap filterByClass(final ItemMap items) {
		ItemMap filtered = new ItemMap();
		EClass profileClass = Model.getProfile().getSpec().getSpecClass(); 
		
		for (Item item : items.values()) {
			boolean valid = false;
			
			for (EClass c : item.getUsableByClass()) {
				if (c == profileClass) {
					valid = true;
				}
			}
			
			if (valid || item.getUsableByClass().size() == 0) {
				filtered.put(item.toString(), item);
			}
		}
		
		return filtered;
	}

	/* ************************** GETTERS / SETTERS ************************ */
	
	/**
	 * @return Allowed sources.
	 */
	public LinkedList<SourceCondition> getSourceConditions() {
		return this.sourceConditions;
	}

	/**
	 * Change the regular expression filter.
	 * @param re Regular expression.
	 */
	public void setRegExFilter(final String re) {
		this.regEx = ".*" + re.toLowerCase() + ".*";
		
		try {
			this.filterByRegEx();
		} catch (Exception e) { }
	}

	/**
	 * Remove items that do not match the regular expression.
	 */
	private void filterByRegEx() {
		final ItemMap newMap = new ItemMap();
		this.refresh();
		
		for (Item i : this.filteredItems.values()) {
			if (i.getName().toLowerCase().matches(this.regEx)) {
				newMap.put(i.getName(), i);
			}
		}
		
		this.filteredItems = newMap;
		
		Model.fireEvent(EModelObserverEvent.ITEM_BROWSER_MODEL_CHANGED);
	}

	/**
	 * Set the item type condition.
	 * @param t Item type.
	 */
	public void setTypeCondition(final String t) {
		this.typeCondition = t;
	}

	/**
	 * Get the item type filter.
	 * @return Item type filter.
	 */
	public String getItemTypeCondition() {
		return this.typeCondition;
	}
}
